package edu.jvn.ict.locationsharing;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Calendar;
import java.util.Collections;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.Vector;

import android.app.Activity;
import android.content.Context;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.LocationManager;
import edu.jvn.ict.locationsharing.informationexchange.ExchangeMessage;
import edu.jvn.ict.locationsharing.informationexchange.GoingOfflineMessage;
import edu.jvn.ict.locationsharing.informationexchange.GoingOnlineMessage;
import edu.jvn.ict.locationsharing.informationexchange.JoiningGroupMessage;
import edu.jvn.ict.locationsharing.informationexchange.LeavingGroupMessage;
import edu.jvn.ict.locationsharing.informationexchange.RequestDictionaryMessage;
import edu.jvn.ict.locationsharing.informationexchange.SendBackDictinonaryMessage;
import edu.jvn.ict.locationsharing.informationexchange.UpdateLocationMessage;

public class MemberServer implements Runnable {

	public static Activity activity;

	private static ServerSocket server = null;

	@SuppressWarnings("rawtypes")
	public static Class androidActivityClass;

	private static LocationManager locationManager;
	private static ExchangeLocationListener locationListener;
	private static String provider;
	private static boolean isExchangeLocation = false;

	public static long intervalUpdate = 5000; // milliseconds
	private static Timer intervalUpdateTimer;
	public static boolean isColInfoUpdate = false;
	public static boolean isRowInfoUpdate = false;

	public static void startExchangeLocation() {
		if (isExchangeLocation) {
			return;
		}

		// register exchange location information when location changed
		locationManager = (LocationManager) activity
				.getSystemService(Context.LOCATION_SERVICE);

		locationListener = new ExchangeLocationListener();
		Criteria criteria = new Criteria();
		criteria.setAccuracy(Criteria.ACCURACY_FINE);
		provider = locationManager.getBestProvider(criteria, true);

		locationManager
				.requestLocationUpdates(provider, 0, 0, locationListener);

		// start interval update timer
		intervalUpdateTimer = new Timer();
		intervalUpdateTimer.schedule(new TimerTask() {

			@Override
			public void run() {
				UpdateLocationMessage updateLocationMessage = new UpdateLocationMessage();

				// Random generator = new Random();
				// int longitude = generator.nextInt(50);
				// int latitude = generator.nextInt(50);
				// int altitude = generator.nextInt(50);
				// thisMember.setGpsCoordinate(new GPSCoordinate(altitude,
				// latitude, longitude));

				updateLocationMessage.setNodeId(thisMember.getId());
				updateLocationMessage.setGpsCoordinate(thisMember
						.getGpsCoordinate());
				updateLocationMessage
						.setTimeStamp(thisMember.getGpsTimestamp());

				updateLocationMessage.setAlongColumn(!isColInfoUpdate);
				updateLocationMessage.setAlongRow(!isRowInfoUpdate);

				exchangeInformation(updateLocationMessage, 1, true);

				isColInfoUpdate = false;
				isRowInfoUpdate = false;

			}
		}, intervalUpdate, intervalUpdate);

		isExchangeLocation = true;
	}

	public static void stopExchangeLocation() {
		if (!isExchangeLocation) {
			return;
		}

		locationManager.removeUpdates(locationListener);
		intervalUpdateTimer.cancel();

		isExchangeLocation = false;
	}

	private static SmsReceiver smsReceiver = new SmsReceiver();

	public static void registerSmsReceiver() {
		IntentFilter filter = new IntentFilter(
				"android.provider.Telephony.SMS_RECEIVED");
		activity.registerReceiver(smsReceiver, filter);
	}

	public static void unregisterSmsReceiver() {
		activity.unregisterReceiver(smsReceiver);
	}

	private static boolean isListen = false;

	private static void listenSocket(final int port) {
		if (isListen) {
			return;
		}

		isListen = true;

		new Thread(new Runnable() {

			@Override
			public void run() {
				
				if (server == null) {
					try {
						server = new ServerSocket(port);
					} catch (IOException e) {
						System.out.println("Could not listen on port " + port);
						System.exit(-1);
					}
				}
				while (isListen) {
					MemberServer processor;
					try {
						processor = new MemberServer(server.accept());
						Thread detectingThread = new Thread(processor);
						detectingThread.start();
					} catch (IOException e) {
						System.out.println("Accept failed: " + port);
						System.exit(-1);
					}
				}
			}
		}).start();

	}

	private static void stopListeningSocket() {
		// if (isListen) {
		// try {
		// server.close();
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }

		isListen = false;
	}

	public static Vector<MemberInfo> dictionary;
	public static TreeMap<Long, MemberInfo> map = new TreeMap<Long, MemberInfo>();
	public static MemberInfo thisMember;

	private static MemberInfo initialThisMember() {
		MemberInfo memberInfo = new MemberInfo();

		try {
			memberInfo.setIp(Utility.ipToInt(Utility.getLocalIpAddress()));
		} catch (SocketException e) {
			e.printStackTrace();
		}

		// memberInfo.setPhoneNumber(Utility.getMyPhoneNumber());
		// memberInfo.setId(memberInfo.getPhoneNumber());
		memberInfo.setNodeStatus(MemberInfo.OFFLINE);

		return memberInfo;
	}

	public static void startAGroup(String phoneNumber, int port) {
		if (activity == null || androidActivityClass == null) {
			return;
		}

		thisMember = initialThisMember();
		thisMember.setPort(port);
		thisMember.setPhoneNumber(Long.valueOf(phoneNumber));
		thisMember.setId(thisMember.getPhoneNumber());
		thisMember
				.setJoiningTimestamp(Calendar.getInstance().getTimeInMillis());
		thisMember.setNodeStatus(MemberInfo.ONLINE);

		dictionary = new Vector<MemberInfo>();
		dictionary.add(thisMember);
		map.put(thisMember.getId(), thisMember);

		registerSmsReceiver();
		listenSocket(port);
		startExchangeLocation();
	}

	public static void joinGroupOf(String phoneNumber, int port) {
		if (activity == null || androidActivityClass == null) {
			return;
		}

		thisMember = initialThisMember();
		thisMember.setPort(port);
		thisMember
				.setJoiningTimestamp(Calendar.getInstance().getTimeInMillis());
		// fake GPS coordinate
		thisMember.setGpsCoordinate(new GPSCoordinate(1, 1, 1));

		/* fake dictionary */
		dictionary = new Vector<MemberInfo>();
		dictionary.add(thisMember);
		// map.put(thisMember.getId(), thisMember);
		// create hello message
		// String helloMessage = Utility.JOINING_GROUP_REQUEST + "," +
		// Utility.ipToString(MemberServer.thisMember.getIp()) + "," +
		// MemberServer.thisMember.getJoiningTimestamp();
		JoiningGroupMessage joiningGroupMessage = new JoiningGroupMessage();
		joiningGroupMessage.setIp(MemberServer.thisMember.getIp());
		joiningGroupMessage.setPort(port);
		joiningGroupMessage.setJoiningTimestamp(MemberServer.thisMember
				.getJoiningTimestamp());

		// send joining message
		Utility.sendSMS(phoneNumber, joiningGroupMessage.toString());

		listenSocket(port);
		startExchangeLocation();
	}

	public static void leavingGroup() {
		unregisterSmsReceiver();
		stopListeningSocket();
		stopExchangeLocation();

		LeavingGroupMessage leavingGroupMessage = new LeavingGroupMessage(
				thisMember.getId());

		exchangeInformation(leavingGroupMessage, 1, true);

		dictionary = new Vector<MemberInfo>();
		map = new TreeMap<Long, MemberInfo>();
	}

	public static void goOffline() {
		unregisterSmsReceiver();
		stopListeningSocket();
		stopExchangeLocation();

		GoingOfflineMessage goingOfflineMessage = new GoingOfflineMessage(
				thisMember.getId());

		exchangeInformation(goingOfflineMessage, 1, true);

		thisMember.setNodeStatus(MemberInfo.OFFLINE);
	}

	public static boolean goOnline(int port) {

		if (updateDictionaryFromOtherMembers()) {
			GoingOnlineMessage goingOnlineMessage = new GoingOnlineMessage(
					thisMember.getId());

			exchangeInformation(goingOnlineMessage, 1, true);

			thisMember = map.get(thisMember.getId());
			thisMember.setNodeStatus(MemberInfo.ONLINE);

			registerSmsReceiver();
			listenSocket(port);
			startExchangeLocation();

			return true;
		}

		return false;
	}

	@SuppressWarnings("unchecked")
	private static boolean updateDictionaryFromOtherMembers() {
		Socket clientSocket;
		try {
			for (MemberInfo member : dictionary) {
				try {
					clientSocket = new Socket(
							Utility.ipToString(member.getIp()),
							member.getPort());
				} catch (Exception e) {
					continue;
				}

				ObjectOutputStream out = new ObjectOutputStream(
						clientSocket.getOutputStream());
				ObjectInputStream in = new ObjectInputStream(
						clientSocket.getInputStream());

				out.writeObject(new RequestDictionaryMessage());
				int nodeStatus = in.readInt();

				if (nodeStatus == MemberInfo.ONLINE) {
					dictionary = (Vector<MemberInfo>) in.readObject();

					map = new TreeMap<Long, MemberInfo>();
					for (MemberInfo memberInfo : dictionary) {
						map.put(memberInfo.getId(), memberInfo);
					}

					out.close();
					clientSocket.close();
					return true;
				}

				out.close();
				clientSocket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		return false;
	}

	public static void exchangeInformation(ExchangeMessage exchangeMessage,
			int k, boolean rebroadcast) {
		try {
			boolean isRebroadcast = exchangeMessage.isRebroadcast();
			exchangeMessage.setRebroadcast(rebroadcast);

			if (isRebroadcast) {
				int cid = dictionary.indexOf(thisMember);
				int[] rowCol = Utility.getRowCol(cid);
				Socket clientSocket;

				if (exchangeMessage.isAlongRow()) {
					isColInfoUpdate = true;
					int i = 0;

					while (i < k) {
						int colExchange = 0;
						int cidExchange = Utility.getCID(rowCol[0] + i,
								colExchange);

						while (cidExchange < dictionary.size()) {
							if (cidExchange != cid) {
								MemberInfo exchangeMember = dictionary
										.get(cidExchange);

								if (exchangeMember.getNodeStatus() == MemberInfo.ONLINE) {
									clientSocket = new Socket(
											Utility.ipToString(exchangeMember
													.getIp()),
											exchangeMember.getPort());
									ObjectOutputStream out = new ObjectOutputStream(
											clientSocket.getOutputStream());

									exchangeMessage.setAlongColumn(true);
									exchangeMessage.setAlongRow(false);
									out.writeObject(exchangeMessage);

									out.close();
									clientSocket.close();
								}

							}

							colExchange++;
							cidExchange = Utility.getCID(rowCol[0] + i,
									colExchange);
						}

						i++;
					}
				}

				if (exchangeMessage.isAlongColumn()) {
					isRowInfoUpdate = true;
					int i = 0;

					while (i < k) {
						int rowExchange = 0;
						int cidExchange = Utility.getCID(rowExchange, rowCol[1]
								+ i);

						while (cidExchange < dictionary.size()) {

							if (cidExchange != cid) {
								MemberInfo exchangeMember = dictionary
										.get(cidExchange);

								if (exchangeMember.getNodeStatus() == MemberInfo.ONLINE) {
									clientSocket = new Socket(
											Utility.ipToString(exchangeMember
													.getIp()),
											exchangeMember.getPort());
									ObjectOutputStream out = new ObjectOutputStream(
											clientSocket.getOutputStream());

									exchangeMessage.setAlongColumn(false);
									exchangeMessage.setAlongRow(true);
									out.writeObject(exchangeMessage);

									out.close();
									clientSocket.close();
								}
							}

							rowExchange++;
							cidExchange = Utility.getCID(rowExchange, rowCol[1]
									+ i);
						}

						i++;
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public static MemberInfo createMemberFromJGM(
			JoiningGroupMessage joiningGroupMessage) {
		MemberInfo memberInfo = new MemberInfo();
		memberInfo.setId(joiningGroupMessage.getPhoneNumber());
		memberInfo.setIp(joiningGroupMessage.getIp());
		memberInfo.setPort(joiningGroupMessage.getPort());
		memberInfo.setJoiningTimestamp(joiningGroupMessage
				.getJoiningTimestamp());
		memberInfo.setPhoneNumber(joiningGroupMessage.getPhoneNumber());
		memberInfo.setNodeStatus(MemberInfo.ONLINE);
		return memberInfo;
	}

	public static void addToDictionary(MemberInfo memberInfo) {
		map.put(memberInfo.getId(), memberInfo);

		int insertIndex = Collections.binarySearch(dictionary, memberInfo);

		if (insertIndex < 0) {
			dictionary.add(-insertIndex - 1, memberInfo);
		}
	}

	public static void sendDictionary(int ipInt, int port) {
		try {
			Socket clientSocket = new Socket(Utility.ipToString(ipInt), port);

			ObjectOutputStream out = new ObjectOutputStream(
					clientSocket.getOutputStream());

			out.writeObject(new SendBackDictinonaryMessage());
			out.writeObject(dictionary);

			out.close();
			clientSocket.close();

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private Socket client;

	public MemberServer(Socket client) {
		this.client = client;
	}

	@Override
	public void run() {
		ObjectInputStream in = null;
		ObjectOutputStream out = null;

		try {
			out = new ObjectOutputStream(this.client.getOutputStream());
			in = new ObjectInputStream(this.client.getInputStream());

			// process
			ExchangeMessage exchangeMessage = (ExchangeMessage) in.readObject();
			exchangeMessage.process(in, out);

			out.close();
			in.close();
			client.close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

}
